import { CdkDragDrop, CdkDrag, CdkDropList, CdkDropListGroup, CdkDragPreview, CdkDragStart } from '@angular/cdk/drag-drop';
import { NgClass, NgStyle } from '@angular/common';
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, OnInit, viewChild, ViewChild } from '@angular/core';

import { fadeAnimation } from '@app/animations/fade.animation';
import { NgDragDropService } from '@app/core/services/common/drag-drop.service';
import { DraggableDirective } from '@app/shared/directives/draggable.directive';
import { DroppableDirective } from '@app/shared/directives/droppable.directive';
import { uniqueId } from 'lodash';
import { NzGridModule } from 'ng-zorro-antd/grid';
import { NzIconDirective } from 'ng-zorro-antd/icon';
import { NzTabPosition, NzTabsModule } from 'ng-zorro-antd/tabs';

import { DashboardDesignerComponent } from '../dashboard-designer/dashboard-designer.component';
import widgetConfig, { LayoutPosition, RectanglePosition, WidgetConfig, WidgetType } from '../widget-default-setting';
import { ConfigPanelComponent } from '../config-panel/config-panel.component';

interface ElementPosition {
  left: number;
  top: number;
}

interface OffsetPosition {
  height: number;
  width: number;
}

@Component({
  selector: 'app-dashboard-panel',
  standalone: true,
  imports: [
    ConfigPanelComponent,
    DraggableDirective,
    NzTabsModule,
    NzIconDirective,
    NzGridModule,
    DroppableDirective,
    CdkDropListGroup,
    CdkDropList,
    CdkDrag,
    NgStyle,
    NgClass,
    DashboardDesignerComponent,
    CdkDragPreview
  ],
  templateUrl: './dashboard-panel.component.html',
  providers: [NgDragDropService],
  animations: [fadeAnimation],
  styleUrl: './dashboard-panel.component.less'
})
export class DashboardPanelComponent implements OnInit {
  widgetConfig: WidgetConfig[] = [];
  widgetTypeConfig: WidgetType[] = [];
  nzTabPosition: NzTabPosition = 'left';
  widgetList: WidgetConfig[] = [];
  isInContainer: boolean = false;
  flag: string = 'out';
  displayConfig: boolean = false;

  // @ts-ignore
  rightDesign: ElementRef<HTMLElement>;
  // @ts-ignore
  connectionManager: DashboardWidgetsConnectionManager;
  @ViewChild('dashboardDesigner') dashboardDesigner!: ElementRef<HTMLElement>;

  constructor(private readonly cdr: ChangeDetectorRef) {}

  ngOnInit(): void {
    this.widgetConfig = widgetConfig.widgetConfigJson;
    this.widgetTypeConfig = widgetConfig.widgetType;
    this.widgetConfig.forEach(config => {
      config.data = config;
    });
  }

  initRightDesignPanel($event: ElementRef<HTMLElement>): void {
    this.rightDesign = $event;
  }

  dropWidget(event: CdkDragDrop<WidgetConfig[]>): void {
    if (event.previousContainer !== event.container || !this.checkAccept(event.item.element.nativeElement)) {
      return;
    }

    const moveX: number = event.dropPoint.x - event.distance.x;
    const moveY: number = event.dropPoint.y - event.distance.y;
    const mouseEvent: MouseEvent = event.event as MouseEvent;
    const layoutPosition = this.getPercentageLayout(event.item.element, moveX, moveY);
    const layout: LayoutPosition = {
      left: `${mouseEvent.layerX - event.item.element.nativeElement.clientWidth / 2}px`,
      top: `${mouseEvent.layerY - event.item.element.nativeElement.clientHeight / 2}px`,
      width: layoutPosition.width,
      height: layoutPosition.height
    };
    const widgetTag: Attr | null = event.item.element.nativeElement.attributes.getNamedItem('id');
    const widget: WidgetConfig = event.item.data;
    let isExist = false;
    // widgetTag is null: drag widget from left panel to right panel. not null: move widget in right panel.
    if (widgetTag) {
      isExist = this.widgetList.some(r => r.tag === widgetTag.value);
    }
    widget.layout = layout;
    if (!isExist) {
      widget.tag = uniqueId(`${widget.field}_`);
      this.widgetList.push(widget);
      console.log(this.widgetList);
    }
    this.cdr.markForCheck();
    this.displayConfig = true;
  }

  checkAccept(element: HTMLElement): boolean {
    return element.classList.contains('inner-box');
  }

  startDrag(event: CdkDragStart): void {
    let outOffset, inOffset;
    const previewContainer: HTMLElement = document.querySelector('.cdk-drag-preview')!;
    const mouseEvent: MouseEvent = event.event as MouseEvent;
    const rectDetail: DOMRect = this.rightDesign.nativeElement.getBoundingClientRect();
    // the dragging element is in drop container or not.
    this.isInContainer = this.isDragElementInContainer(mouseEvent.clientX, mouseEvent.clientY, rectDetail);
    const inElement: HTMLElement = previewContainer.querySelector('.in')!;
    const outElement: HTMLElement = previewContainer.querySelector('.out')!;
    const defaultHeight: number = parseFloat(event.source.element.nativeElement.getAttribute('defaultHeight')!);
  }

  getOffset(showElement: HTMLElement, hideElement: HTMLElement, defaultHeight?: number): OffsetPosition {
    let offset, height;
    if (showElement.classList.contains('hide')) {
      if (defaultHeight) {
        height = defaultHeight;
      }
      showElement.classList.remove('hide');
      hideElement.classList.add('hide');
      offset = {
        width: showElement.clientWidth,
        height: height ?? showElement.clientHeight
      };
      hideElement.classList.remove('hide');
      showElement.classList.add('hide');
    } else {
      offset = {
        width: showElement.clientWidth,
        height: showElement.clientHeight
      };
    }

    return offset;
  }

  animateSelf(element: HTMLElement, properties: { [key: string]: any }, duration: number = 500, callback: () => void): void {
    const startTime = performance.now();
    const frameDuration = 10; // 每秒10帧
    const frameCount = duration / frameDuration;
    let frame = 0;

    requestAnimationFrame(function animateFrame(timestamp) {
      if (frame++ < frameCount) {
        const progress = (timestamp - startTime) / duration;
        const easeProgress = Math.pow(progress, 2); // 使用二次缓动

        for (const property in properties) {
          if (properties.hasOwnProperty(property)) {
            const currentValue = parseFloat(getComputedStyle(element).getPropertyValue(property));
            // @ts-ignore
            element.style[property] = `${currentValue + easeProgress * (properties[property] - currentValue)}px`;
          }
        }

        if (progress < 1) {
          requestAnimationFrame(animateFrame);
        }
      } else {
        callback();
      }
    });
  }

  isDragElementInContainer(clientX: number, clientY: number, rect: DOMRect): boolean {
    return clientX > rect.left && clientX < rect.right && clientY < rect.bottom && clientY > rect.top;
  }

  // checkOverlap()

  toPercentage(num: number): string {
    return `${Number(100 * num).toFixed(2)}%`;
  }

  getPercentageLayout(widgetElementRef: ElementRef<HTMLElement>, moveX: number, moveY: number): LayoutPosition {
    const widgetElement: HTMLElement = widgetElementRef.nativeElement;
    const containerElement: HTMLElement = this.rightDesign.nativeElement;
    const left: number = widgetElement.parentElement!.offsetLeft;
    const top: number = widgetElement.parentElement!.offsetTop;
    const width: number = widgetElement.offsetWidth;
    const height: number = widgetElement.offsetHeight;
    const containerHeight: number = containerElement.offsetHeight;
    const containerWidth: number = containerElement.offsetWidth;

    return {
      left: this.toPercentage((left + moveX - 300) / containerWidth),
      top: this.toPercentage(top / containerHeight),
      width: this.toPercentage(width / containerWidth),
      height: this.toPercentage(height / containerHeight)
    };
  }
}
